using System;
using System.Collections.Generic;
using System.Linq;
using Db4objects.Db4o;
using SharedObjects;
using TheGame.Server;

namespace TheGameLib
{
	/// <summary>
	/// This class is basically a session control for connected players (Agents) with additional functionality
	/// in regards to tracing, chatting etc
	/// </summary>
	public class AgentConnection
	{
		//local callback to receive chat messages for this connection
		public event EventHandler<ChatEventArgs> ChatMessageReceived;

		public event EventHandler<TraceEventArgs> TraceUpdated;
		public event EventHandler<TraceEndedEventArgs> TraceEnded;

		public Guid ConnectionId { get; private set; }
		public DateTime ConnectionStartTime { get; private set; } //time at which the connection was initiated
		public DateTime BypassStartTime { get; private set; } //time at which bypassing started
		public DateTime LoginStartTime { get; private set; } //time at which login happened

		public string ChatNick { get; set; }

		public SystemConnectionState CurrentConnectionState { get; set; }
		public TraceState CurrentTraceState { get; private set; }
		public double TotalTraceTime; //total time for this connection, depending on length of hop list etc. Determined by World.User.Connection
		public ulong TargetSystemId { get; private set; } 

		private const double DiminishingDecrease = 0.87742637; //(last hop @ 10 hops has 1 second duration)
		public bool IsHacked { get; private set; }
		//Specifies ComputerSystemAccount currently being used for bypasses.
		//ex: if CredentialFile belongs to CSA x, then subsequent bypasses must match the CSA id, or bypassing fails.
		public ulong ComputerSystemAccountId;

		public List<ulong> ConnectedNodes { get; set; }

		//guid = which secsys, bool = isBypassed, keeps track of which secsys is successfully bypassed
		private Dictionary<ulong, bool> SecuritySystemStates { get; set; }

		/// <summary>
		/// when a new connection is made by an agent, this object holds realtime state information about the connection.
		/// </summary>
		/// <param name="connectedNodes">list of systems connected to (hop list)</param>
		/// <param name="securitySystemImplementations">the list of security systems for the target host</param>
		internal AgentConnection(List<ulong> connectedNodes, List<ulong> securitySystemImplementations)
		{
			ConnectionId = Guid.NewGuid();
			CurrentConnectionState = SystemConnectionState.Connected;
			CurrentTraceState = TraceState.Idle;
			ConnectionStartTime = DateTime.Now;

			if (securitySystemImplementations.Count == 0)
				CurrentConnectionState = SystemConnectionState.Bypassed;

			SecuritySystemStates = new Dictionary<ulong, bool>();
			foreach (ulong secSysId in securitySystemImplementations)
				SecuritySystemStates.Add(secSysId, false);
			ConnectedNodes = connectedNodes;
			TargetSystemId = connectedNodes.Last();
			for (int i = 0; i < connectedNodes.Count; i++)
			{
				double intermediate = Math.Pow(DiminishingDecrease, i) * 10000;
				TotalTraceTime += intermediate;
			}
		}

		public void SetBypassSuccessful(ulong secSysId)
		{
			if (SecuritySystemStates.Where(it => it.Value == false).Count() == SecuritySystemStates.Count) //if none are bypassed yet
			{
				CurrentConnectionState = SystemConnectionState.Bypassing; //start bypassing
				BypassStartTime = DateTime.Now;
			}
			SecuritySystemStates[secSysId] = true;

			if (SecuritySystemStates.Where(it => it.Value).Count() == SecuritySystemStates.Count) //if all are bypassed
				CurrentConnectionState = SystemConnectionState.Bypassed; //we are finished bypassing
			IsHacked = true;
		}

		public void SetAccessSuccessfull(ulong secSysId, bool isTemporaryAccount)
		{
			if (SecuritySystemStates.Where(it => it.Value == false).Count() == SecuritySystemStates.Count) //if none are bypassed yet
			{
				CurrentConnectionState = SystemConnectionState.LoggingIn;
			}
			SecuritySystemStates[secSysId] = true;

			if (SecuritySystemStates.Where(it => it.Value).Count() == SecuritySystemStates.Count) //if all are bypassed
				CurrentConnectionState = SystemConnectionState.Bypassed; //we are finished bypassing
			
			if (isTemporaryAccount)
				IsHacked = true;
		}

		public bool IsValidSecuritySystem(ulong secSysId)
		{
			if (SecuritySystemStates.ContainsKey(secSysId))
				return true;
			return false;
		}

		/// <summary>
		/// Returns the Id of the host before the target
		/// </summary>
		/// <param name="targetId">id of the target</param>
		/// <returns>id of host before target</returns>
		public ulong GetConnectedHostId(ulong targetId)
		{
			int idx = ConnectedNodes.IndexOf(targetId);
			if (idx < 0)
				return 0;
			idx -= 1;
			if (idx < 0)
				return ConnectedNodes[idx + 1];
			return ConnectedNodes[idx];
		}

		public void Login()
		{
			//todo: potentially more things to do (like forced tracing start etc), but for now its just directly logging in
			LoginStartTime = DateTime.Now;
			CurrentConnectionState = SystemConnectionState.LoggedIn;
		}

		public void Disconnect()
		{
			//if (traceTimer != null)
			//    traceTimer.Stop();
			//traceTimer = new Timer();
			CurrentConnectionState = SystemConnectionState.Disconnected;
		}

		#region Handle tracing
		//private Timer traceTimer;
		//private int traceCurrentHop = 1; //starts at 1 and traces up to numHops
		public void Trace(IObjectContainer db)
		{
			if (CurrentTraceState == TraceState.TraceRunning)
				return;

			CurrentTraceState = TraceState.TraceRunning;
			Tracer tracer = new Tracer(db, ConnectedNodes);
			tracer.TraceStarted += TracerTraceStarted;
			tracer.TraceUpdated += TracerTraceUpdated;
			tracer.TraceEnded += TracerTraceEnded;
			tracer.Start();
		}

		void TracerTraceEnded(object sender, TracerEventArgs e)
		{
			CurrentTraceState = TraceState.TraceFinished;
			if (TraceEnded != null)
				TraceEnded(this, new TraceEndedEventArgs {ConnectionId = ConnectionId, TraceTimeElapsed = true});
		}

		void TracerTraceStarted(object sender, TracerEventArgs e)
		{
			CurrentTraceState = TraceState.TraceRunning;
			if (TraceUpdated != null)
				TraceUpdated(this, new TraceEventArgs {RemainingMilliseconds = e.RemainingMilliseconds});
		}

		void TracerTraceUpdated(object sender, TracerEventArgs e)
		{
			CurrentTraceState = TraceState.TraceRunning;
			if(TraceUpdated != null)
				TraceUpdated(this, new TraceEventArgs { RemainingMilliseconds = e.RemainingMilliseconds });
		}
		#endregion

		/// <summary>
		/// outbound local chatmessage
		/// </summary>
		/// <param name="message"></param>
		public void SendChatMessage(ChatMessage message)
		{
			if (ChatMessageReceived != null)
				ChatMessageReceived(this, new ChatEventArgs {ChatMessage = message});
		}
	}
}